/*
 * Copyright 2007 Jeffrey Palm
 */

import java.io.*;
import java.util.*;

class Parser extends App {

  private final Scanner s;
  private World res;

  Parser(Scanner s) {
    this(false,s);
  }

  Parser(boolean verbose, Scanner s) {
    super(verbose);
    this.s = s;
    this.s.setVerbose(isVerbose());
  }

  // --------------------------------------------------
  // Interface
  // --------------------------------------------------

  public void close() throws IOException {
    s.close();
  }


  public World parse() {
    res = new World();
    parseS();
    return res;
  }

  // --------------------------------------------------
  // Private
  // --------------------------------------------------

  /*
    S     ::= Type* 
    Type  ::= String [ ':' String ] '{' Decl* '}'
    Decl  ::= '.'? String String [ '(' Param? [ ',' Param ]+ ')' ]';'
    Param ::= String | String String
  */

  private void parseS() {
    while (parseType()) {}
  }

  private boolean parseType() {
    String name = readString("name");
    if (name == null) return false;
    Token t = nextToken();
    if (t == null) return false;
    String superType = null;
    if (t.getType() == Token.Type.DELIM && t.getString().equals(":")) {
      superType = readString("superType");
    }
    if (!isDelim(t,'{')) {
      error("Expected '{'",t);
      return false;
    }
    World.Type newType = res.setSuper(name,superType);
    note("adding type: " + newType);
    while (parseDecl(newType)) {}
    // Already read the '}' in parseDecl
    return true;
  }

  private boolean parseDecl(World.Type enc) {
    Token t;
    t = nextToken();
    if (isDelim(t,'}')) return false;
    String type = t.getString();
    if (type == null) return false;
    int mods = 0;
    if (type.equals("-")) {
      mods |= World.Mods.SYNTHETIC;
      type = readString("type");
    } else if (type.equals("+")) {
      mods |= World.Mods.MUTABLE;
      type = readString("type");
    }
    String name = readString("name");
    //
    // allow a ';' or '}' -- note '}' means we're done
    //
    t = nextToken();
    World.Decl.Mutable d = res.addDecl(enc,type,name,mods);
    if (isDelim(t,';') || isDelim(t,'}')) {
      //
      // this is OK
      //
    } else if (isDelim(t,'(')) {
      while (parseParam(d)) {}
      t = nextToken();
      if (isDelim(t,';') || isDelim(t,'}')) {
        return t.getString().equals(";");
      }
      error("Expected ';' or '}'",t);
      return false;
    } else {
      error("Expected ';' or '}'",t);
      return false;
    }
    
    return t.getString().equals(";");
  }

  private boolean parseParam(World.Decl.Mutable d) {
    Token t1 = nextToken();
    if (isDelim(t1,')')) {
      d.addParam(null,t1.getString());
      return false;
    }
    Token t2 = nextToken();
    if (isDelim(t2,')')) {
      d.addParam(null,t1.getString());
      return false;
    } else if (isDelim(t2,',')) {
      d.addParam(null,t1.getString());
      return true;
    } else if (t2.getType() == Token.Type.STRING) {
      Token t3 = nextToken();
      d.addParam(t1.getString(),t2.getString());
      if (isDelim(t3,')')) return false;
      if (isDelim(t3,',')) return true;
      error("Expected ')' or ','",t3);
      return false;
    } else {
      error("Expected ')' or ',' or <id> ",t2);
      return false;
    }
  }

  private boolean isDelim(Token t, char c) {
    return t != null && t.getType() == Token.Type.DELIM 
      &&   t.getString().equals(String.valueOf(c));
  }

  private void error(String msg, Token t) {
    String s = msg + "... found " + t.getString() + " @ " + t.getLine();
    note(s,true);
    Thread.dumpStack();
    System.exit(1);
  }

  /**
   * @return next token or <tt>null</tt> if we're done
   */
  private String readString(String str) {
    try {
      Token t = s.nextToken();
      if (t == null) return null;
      if (t.getType() != Token.Type.STRING) {
        error("Expected string",t);
        return null;
      }
      String res = t.getString();
      note("read " + str + ":" + res);
      return res;
    } catch (Exception e) {e.printStackTrace();}
    return null;
  }

  /**
   * @param expected expected character
   * @return next token or <tt>0</tt> if we're done
   */
  private char read(char expected) {
    try {
      Token t = s.nextToken();
      if (t == null) return (char)0;
      if (t.getType() != Token.Type.DELIM) {
        error("Expected " + expected,t);
        return (char)0;
      }
      return t.getString().charAt(0);
    } catch (Exception e) {e.printStackTrace();}
    return (char)0;
  }

  private Token nextToken() {
    try {
      return s.nextToken();
    } catch (Exception e) {e.printStackTrace();}
    return null;
  }

  // --------------------------------------------------
  // Testing
  // --------------------------------------------------

  public static void main(String[] args) throws Exception {
    new Parser(true,new Scanner(new FileInputStream("spec.txt"))).realMain(args);
  }

  void go() throws Exception {
    System.out.println(parse());
  }

}